﻿using System;
using System.Linq;
using Gems;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace GemsTest
{
    [TestClass]
    public class SuspectMatrixTests
    {
        [TestMethod]
        public void AgreedByMajorityOnMerge()
        {
            const int ticks = 2;
            var agreed = false;

            var ms = CreateMatrices(3, ticks, i => agreed = i == 2);

            for (var i = 0; i < ticks; i++)
            {
                ms[0].Tick();
                ms[1].Tick();
            }

            Assert.IsFalse(agreed);
            ms[0].Merge(ms[1]);

            Assert.IsTrue(agreed);
        }

        [TestMethod]
        public void AgreedByMajorityOnTick()
        {
            bool agreed = false;
            var ms = CreateMatrices(3, 2, i => agreed = true);

            ms[0].Tick();
            ms[1].Tick();
            ms[1].Tick();


            ms[0].Merge(ms[1]);

            Assert.IsFalse(agreed);

            ms[0].Tick();
            ms[0].Tick();

            Assert.IsTrue(agreed);
        }

        [TestMethod]
        public void AgreedByMajorityOnlyOnce()
        {
            bool agreed = false;
            const int ticks = 2;
            var ms = CreateMatrices(3, ticks, i => agreed = true);

            for (var i = 0; i < ticks; i++)
            {
                ms[0].Tick();
                ms[1].Tick();
            }

            Assert.IsFalse(agreed);
            ms[0].Merge(ms[1]);

            agreed = false;

            for (var i = 0; i < ticks; i++)
            {
                ms[0].Tick();
                ms[1].Tick();
            }

            
            ms[0].Merge(ms[1]);
            Assert.IsFalse(agreed);
        }

        [TestMethod]
        public void AgreedByTimeout()
        {
            const int ticks = 2;
            var agreed = false;

            var ms = CreateMatrices(2, ticks, i => agreed = i == 1);

            for (var i = 0; i < ticks; i++)
            {
                ms[0].Tick();
            }

            Assert.IsFalse(agreed);
            
            for (var i = 0; i < ticks*2; i++)
            {
                ms[0].Tick();
            }

            Assert.IsTrue(agreed);
        }

        
        [TestMethod]
        public void AgreedByTimeoutOnlyOnce()
        {
            const int ticks = 2;
            var agreed = false;

            var ms = CreateMatrices(2, ticks, i => agreed = i == 1);

            for (var i = 0; i < ticks; i++)
            {
                ms[0].Tick();
            }

            Assert.IsFalse(agreed);

            for (var i = 0; i < ticks * 2; i++)
            {
                ms[0].Tick();
            }

            Assert.IsTrue(agreed);

            agreed = false;

            for (var i = 0; i < ticks*4; i++)
            {
                ms[0].Tick();
            }

            Assert.IsFalse(agreed);
        }

        private static SuspectMatrix[] CreateMatrices(int size, int gossipTimeout, Action<int> onAgreed)
        {
            var matrices = (from i in Enumerable.Range(0, size)
                         select new SuspectMatrix(size, i, gossipTimeout, gossipTimeout*2)).ToArray();

            foreach (var mx in matrices)
            {
                mx.Agreed += onAgreed;
            }

            return matrices;

        }
    }
}